var SEQUENCE_ATTR = "sequence";
var INDEX_ATTR = "index";
var PARENT_ATTR = "parent";
var LEVEL_PREFIX = "line";
var currentNode = {
    sequence : null,
    parent : null,
    child : null,
    upSibling : null,
    downSibling : null
};

/**
 * Get the node info for up, down, in, out operations. Set the info into the public var
 * currentNode.
 *
 */
function changeCurrentNode(sequence, node) {
    var nodeInfo = null;

    if(sequence == null)
        nodeInfo = getNodeInfo($(node).attr(SEQUENCE_ATTR));
    else
        nodeInfo = getNodeInfo(sequence);

    currentNode.sequence = nodeInfo.sequence;
    currentNode.parent = nodeInfo.parent;
    currentNode.child = nodeInfo.child;
    currentNode.upSibling = nodeInfo.upSibling;
    currentNode.downSibling = nodeInfo.downSibling;
}

function getNodeInfo(sequence) {
    var parent = null;
    var child = null;
    var upSibling = null;
    var downSibling = null;

    if (sequence.length > 1) {
        parent = sequence.substring(0, sequence.lastIndexOf("."));
    } else {
        parent = ".";
    }

    if (sequence.split(".").length < 5) {
        child = sequence + ".1";
        if (getNodeBySequence(child).length == 0) {
            child = null;
        }
    }

    downSibling = sequence.substring(0, sequence.lastIndexOf(".") + 1) +
    eval(sequence.substring(sequence.lastIndexOf(".") + 1, sequence.length) + "+1");
    if(getNodeBySequence(downSibling).length == 0)
        downSibling = null;

    upSibling = sequence.substring(0, sequence.lastIndexOf(".") + 1) +
    eval(sequence.substring(sequence.lastIndexOf(".") + 1, sequence.length) + "-1");
    if(getNodeBySequence(upSibling).length == 0)
        upSibling = null;

    return {
        sequence : sequence,
        parent : parent,
        child : child,
        upSibling : upSibling,
        downSibling : downSibling
    };
}

// ********** Main Functions ********** //
function up() {
    if (currentNode.upSibling != null) {
        verticalSwitch(currentNode.sequence, currentNode.upSibling);
        changeCurrentNode(currentNode.upSibling);
    } else if (getNodeInfo(currentNode.parent).upSibling != null) {
        // If the currentNode has no any upSiblings, but its parent has an upSibling. Can be moved up.
        var parentUpSiblingId = getNodeInfo(currentNode.parent).upSibling;

        // ********** Begin ********** //
        // This part is used to find out the next sequence for the parent's up sibling.

        // Get the next child id for the current node' parent's upSibling.
        var nextChildId = null;
        // pufcId -> parent upsibling first child.
        var pufcId = getNodeInfo(parentUpSiblingId).child;
        if(pufcId == null) {
            nextChildId = parentUpSiblingId + ".1";
        } else {
            var currentNodeId = pufcId;
            while (true) {
                var downSiblingId = getNodeInfo(currentNodeId).downSibling;
                if (downSiblingId == null) {
                    break;
                }
                currentNodeId = downSiblingId;
            }
            var lastBit = currentNodeId.substring(currentNodeId.length - 1, currentNodeId.length);
            var nextChildId = currentNodeId.substring(0, currentNodeId.length - 1) + eval(lastBit + "+1");
        }
        // ********** End ********** //

        // Move the current node and its children upon its parent.
        getNodeBySequence(currentNode.sequence).insertBefore(getNodeBySequence(currentNode.parent));
        getChildNodesBySequence(currentNode.sequence).insertBefore(getNodeBySequence(currentNode.parent));

        // Update the attributes of the current node (the node need to be moved up).
        getNodeBySequence(currentNode.sequence).each(function() {
            var oldSeq = $(this).attr(SEQUENCE_ATTR);
            var newSeq = nextChildId + oldSeq.substring(currentNode.sequence.length, oldSeq.length);

            // Replace the sequece values used to displayed to the users.
            $("th", this).html(newSeq);
            // Update the input value.
            $("input", this).val(newSeq);
            // Replace the sequence attribute for each of the node need to be moved up.
            $(this).attr(SEQUENCE_ATTR, newSeq);
            $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
        });

        // Update the attributes of the children of the current node.
        getChildNodesBySequence(currentNode.sequence).each(function() {
            var oldSeq = $(this).attr(SEQUENCE_ATTR);
            var newSeq = nextChildId + oldSeq.substring(currentNode.sequence.length, oldSeq.length);

            // Replace the sequece values used to displayed to the users.
            $("th", this).html(newSeq);
            // Update the input value.
            $("input", this).val(newSeq);
            // Replace the sequence attribute for each of the node need to be moved up.
            $(this).attr(SEQUENCE_ATTR, newSeq);
            $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
        });

        // Update the attributes of siblings of the current node.
        getChildNodesBySequence(currentNode.parent).each(function() {
            var newPre = null;
            var seq = $(this).attr(SEQUENCE_ATTR);

            if(seq.indexOf(".") != -1) {
                // Get the part of the sequence need to be changed.
                var lastPart = seq.substring(currentNode.sequence.lastIndexOf(".") + 1, seq.length);
                if (lastPart.indexOf(".") != -1) {
                    newPre = seq.substring(0, currentNode.sequence.lastIndexOf(".") + 1) +
                    eval(lastPart.substring(0, lastPart.indexOf(".")) + "-1")  +
                    lastPart.substring(lastPart.indexOf("."), lastPart.length);
                } else {
                    newPre = seq.substring(0, currentNode.sequence.lastIndexOf(".") + 1) +
                    eval(lastPart + "-1");
                }
            } else {
                newPre = eval(seq.substring(seq.length - 1, seq.length) + "-1");
            }

            var oldSeq = $(this).attr(SEQUENCE_ATTR);

            var newSeq = null;
            if(lastPart.indexOf(".") != -1)
                newSeq = newPre + lastPart.substring(newPre.length, oldSeq.length);
            else
                newSeq = newPre;

            $(this).attr(SEQUENCE_ATTR, newSeq);
            $("th", this).html(newSeq);
            $("input", this).val(newSeq);
            $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
        });

        changeCurrentNode(nextChildId);
    } else if (currentNode.parent != ".") {
        // Move the currentNode before its parent node.
        getNodeBySequence(currentNode.sequence).insertBefore(getNodeBySequence(currentNode.parent));
        getChildNodesBySequence(currentNode.sequence).insertBefore(getNodeBySequence(currentNode.parent));

        // Update the attributes of the siblings of the parent of the currentNode.
        // Find the last sibling of the parent of the current node.
        var lastNodeIndex = currentNode.parent;
        while (getNodeInfo(lastNodeIndex).downSibling) {
            lastNodeIndex = getNodeInfo(lastNodeIndex).downSibling;
        }

        var nodeIndex = lastNodeIndex;
        while (nodeIndex != null) {
            var newSeq = nodeIndex.substring(0, nodeIndex.lastIndexOf(".") + 1) +
            eval(nodeIndex.substring(nodeIndex.lastIndexOf(".") + 1, nodeIndex.length) + "+1");
            updateSequence(nodeIndex, newSeq);
            nodeIndex = getNodeInfo(nodeIndex).upSibling;
        }

        // Update the currentNode attributes.
        if (getNodeInfo(currentNode.parent).parent != ".") {
            updateSequence(getNodeInfo(currentNode.parent).parent + ".2.1", currentNode.parent);
        } else {
            updateSequence("2.1", currentNode.parent);
        }

        // Update siblings of the node you want to move up.
        // FOS -> First Original Sibling
        var fos = getNodeInfo(currentNode.parent).downSibling + ".2";
        fos = getNodeBySequence(fos).length == 0 ? null : fos;

        while (fos != null) {
            updateSequence(fos, fos.substring(0, fos.lastIndexOf(".") + 1) +
                eval(fos.substring(fos.lastIndexOf(".") + 1) + "-1"));
            fos = getNodeInfo(fos).downSibling;
        }

        changeCurrentNode(currentNode.parent);
    }
}

function down() {
    if (currentNode.downSibling != null) {
        verticalSwitch(currentNode.downSibling, currentNode.sequence);
        changeCurrentNode(currentNode.downSibling);
    } else if (getNodeInfo(currentNode.parent).downSibling != null) {
        // If the current node has no any down siblings, but its parent has an down sibling. Then move down.
        var parentDownSiblingId = getNodeInfo(currentNode.parent).downSibling;

        // Move the current node and its children upon its parent.
        getNodeBySequence(parentDownSiblingId).insertBefore(getNodeBySequence(currentNode.sequence));

        // Update the attributes of siblings of the current node.
        getChildNodesBySequence(parentDownSiblingId).each(function() {
            var newPre = null;
            var seq = $(this).attr(SEQUENCE_ATTR);

            if(seq.indexOf(".") != -1) {
                // Get the part of the sequence need to be changed.
                var lastPart = seq.substring(currentNode.sequence.lastIndexOf(".") + 1, seq.length);
                if (lastPart.indexOf(".") != -1) {
                    newPre = seq.substring(0, currentNode.sequence.lastIndexOf(".") + 1) +
                    eval(lastPart.substring(0, lastPart.indexOf(".")) + "+1")  +
                    lastPart.substring(lastPart.indexOf("."), lastPart.length);
                } else {
                    newPre = seq.substring(0, currentNode.sequence.lastIndexOf(".") + 1) +
                    eval(lastPart + "+1");
                }
            } else {
                newPre = eval(seq.substring(seq.length - 1, seq.length) + "+1");
            }

            var oldSeq = $(this).attr(SEQUENCE_ATTR);

            var newSeq = null;
            if(lastPart.indexOf(".") != -1)
                newSeq = newPre + lastPart.substring(newPre.length, oldSeq.length);
            else
                newSeq = newPre;

            $(this).attr(SEQUENCE_ATTR, newSeq);
            $("th", this).html(newSeq);
            $("input", this).val(newSeq);
            $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
        });

        var newRootSeq = parentDownSiblingId + ".1";
        // Update the attributes of the current node (the node need to be moved down).
        getNodeBySequence(currentNode.sequence).each(function() {
            // Replace the sequece values used to displayed to the users.
            $("th", this).html(newRootSeq);
            // Update the input value.
            $("input", this).val(newRootSeq);
            // Replace the sequence attribute for each of the node need to be moved up.
            $(this).attr(SEQUENCE_ATTR, newRootSeq);
            $(this).attr(PARENT_ATTR, parentDownSiblingId);
        });

        // Update the attributes of the children of the current node.
        getChildNodesBySequence(currentNode.sequence).each(function() {
            var oldSeq = $(this).attr(SEQUENCE_ATTR);
            var newSeq = newRootSeq + $(this).attr(SEQUENCE_ATTR).substring(currentNode.sequence.length, oldSeq.length);

            // Replace the sequece values used to displayed to the users.
            $("th", this).html(newSeq);
            // Update the input value.
            $("input", this).val(newSeq);
            // Replace the sequence attribute for each of the node need to be moved up.
            $(this).attr(SEQUENCE_ATTR, newSeq);
            $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
        });

        changeCurrentNode(newRootSeq);
    }
}

function out() {
    if (currentNode.parent != ".") {
        var parent = currentNode.parent;
        var newSeq = parent.substring(0, parent.lastIndexOf(".") + 1) +
        eval(parent.substring(parent.lastIndexOf(".") + 1, parent.length) + "+1");

        // lds -> Last Down Sibling
        var lds = getNodeInfo(parent).downSibling;
        // Update sequences of the down siblings of the parent of the current node.
        while (lds != null && getNodeInfo(lds).downSibling != null) {
            lds = getNodeInfo(lds).downSibling;
        }

        // Update sequences of the down siblings of the parent of the current node.
        if(lds != null) {
            do {
                var newSiblingSeq = lds.substring(0, lds.lastIndexOf(".") + 1) +
                eval(lds.substring(lds.lastIndexOf(".") + 1, lds.length) + "+1");
                updateSequence(lds, newSiblingSeq);
                lds = getNodeInfo(lds).upSibling;
            } while (lds != parent);
        }

        // Update sequences of the node need to be moved out and its children.
        updateSequence(currentNode.sequence, newSeq);

        // Get the last child of the current node.
        var lastChild = getNodeInfo(newSeq).child;
        while (lastChild != null && getNodeInfo(lastChild).downSibling != null) {
            lastChild = getNodeInfo(lastChild).downSibling;
        }

        // Update sequences of the down siblings of the curent node.
        var cds = currentNode.downSibling;
        var i = 1;
        lastChild = lastChild == null ? newSeq + ".0" : lastChild;
        while (cds != null) {
            var newSiblingSeq = lastChild.substring(0, lastChild.lastIndexOf(".") + 1) +
            eval(lastChild.substring(lastChild.lastIndexOf(".") + 1, lastChild.length) + "+" + i++);

            updateSequence(cds, newSiblingSeq);

            cds = getNodeInfo(cds).downSibling;
        }

        changeCurrentNode(newSeq);
    }
}

function moveIn() {
    if (currentNode.upSibling != null && moveInAvailable(currentNode.sequence)) {
        var youngestChild = findYoungestChild(currentNode.upSibling);
        var newCurrentSeq = null;

        // Update sequences of the node and its children need to be moved in.
        if (youngestChild == null) {
            updateSequence(currentNode.sequence, currentNode.upSibling + ".1");
            newCurrentSeq = currentNode.upSibling + ".1";
        } else {
            var nextChild = youngestChild.substring(0, youngestChild.lastIndexOf(".") + 1) +
            eval(youngestChild.substring(youngestChild.lastIndexOf(".") + 1, youngestChild.length) + "+1");
            updateSequence(currentNode.sequence, nextChild);
            newCurrentSeq = nextChild;
        }

        if (currentNode.downSibling != null) {
            // ds -> Down Sibling
            var ds = currentNode.downSibling;
            while (ds) {
                var newSeq = ds.substring(0, ds.lastIndexOf(".") + 1) +
                eval(ds.substring(ds.lastIndexOf(".") + 1, ds.length) + "-1");
                updateSequence(ds, newSeq);
                ds = getNodeInfo(ds).downSibling;
            }
        }

        changeCurrentNode(newCurrentSeq);
    }
}


// ********** Main Functions End ********** //

function findYoungestChild(sequence) {
    if (getNodeInfo(sequence).child != null) {
        var child = getNodeInfo(sequence).child;
        while (getNodeInfo(child).downSibling != null) {
            child = getNodeInfo(child).downSibling;
        }
        return child;
    } else {
        return null;
    }
}

function verticalSwitch(nodeId1, nodeId2) {
    // Move the node1 and his children upon the node2.
    getNodeBySequence(nodeId1).insertBefore(getNodeBySequence(nodeId2))
    getChildNodesBySequence(nodeId1).insertBefore(getNodeBySequence(nodeId2));

    // Get the node 2 and his children.
    var upSibling = getNodeBySequence(nodeId2);
    var upSiblings = getChildNodesBySequence(nodeId2);

    var nodeLength = nodeId1.length;
    // Update attributes of the node 1 and his children.
    getNodeBySequence(nodeId1).each(function() {
        var oldSeq = $(this).attr(SEQUENCE_ATTR);
        var newSeq = nodeId2 + oldSeq.substring(nodeLength, oldSeq.length);

        $("th", this).html(newSeq);
        $("input", this).val(newSeq);
        $(this).attr(SEQUENCE_ATTR, newSeq);
        $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
    });

    getChildNodesBySequence(nodeId1).each(function() {
        var oldSeq = $(this).attr(SEQUENCE_ATTR);
        var newSeq = nodeId2 + oldSeq.substring(nodeLength, oldSeq.length);

        $("th", this).html(newSeq);
        $("input", this).val(newSeq);
        $(this).attr(SEQUENCE_ATTR, newSeq);
        $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
    });

    upSibling.each(function() {
        var oldSeq = $(this).attr(SEQUENCE_ATTR);
        var newSeq = nodeId1 + oldSeq.substring(nodeLength, oldSeq.length);

        $("th", this).html(newSeq);
        $("input", this).val(newSeq);
        $(this).attr(SEQUENCE_ATTR, newSeq);
        $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
    });

    upSiblings.each(function() {
        var oldSeq = $(this).attr(SEQUENCE_ATTR);
        var newSeq = nodeId1 + oldSeq.substring(nodeLength, oldSeq.length);

        $("th", this).html(newSeq);
        $("input", this).val(newSeq);
        $(this).attr(SEQUENCE_ATTR, newSeq);
        $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
    });
}

function getNodeBySequence(sequence) {
    return $("[" + SEQUENCE_ATTR + "=" + sequence + "]");
}

function getChildNodesBySequence(sequence) {
    return $("[" + SEQUENCE_ATTR + "^=" + sequence + ".]");
}

/**
 * Instead of the old sequence with the new sequence for the node with the old sequence and its
 * children nodes.
 */
function updateSequence(oldSeq, newSeq) {
    // Update the attributes of the current node (the node need to be moved down).
    getNodeBySequence(oldSeq).each(function() {
        // Replace the sequece values used to displayed to the users.
        $("th", this).html(newSeq);
        // Update the input value.
        $("input", this).val(newSeq);

        var oldLevel = calcLevel(oldSeq);
        var newLevel = calcLevel(newSeq);
        $(this).removeClass(LEVEL_PREFIX + oldLevel);
        $(this).addClass(LEVEL_PREFIX + newLevel);
        $("th", this).removeClass("number" + oldLevel);
        $("th", this).addClass("number" + newLevel);
        $("th", this).attr("width", (3 + (newLevel - 1) * 4) + "%");
        $("td.row1", this).attr("width", (27 - (newLevel - 1) * 4) + "%");

        // Replace the sequence attribute for each of the node need to be moved up.
        $(this).attr(SEQUENCE_ATTR, newSeq);
        if (newSeq.indexOf(".") > -1) {
            $(this).attr(PARENT_ATTR, newSeq.substring(0, newSeq.lastIndexOf(".")));
        } else {
            $(this).attr(PARENT_ATTR, ".");
        }
    });

    // Update the attributes of the children of the current node.
    getChildNodesBySequence(oldSeq).each(function() {
        var oldSubSeq = $(this).attr(SEQUENCE_ATTR);
        var newSubSeq = newSeq + $(this).attr(SEQUENCE_ATTR).substring(oldSeq.length, oldSubSeq.length);

        // Replace the sequece values used to displayed to the users.
        $("th", this).html(newSubSeq);
        // Update the input value.
        $("input", this).val(newSubSeq);

        var oldLevel = calcLevel(oldSubSeq);
        var newLevel = calcLevel(newSubSeq);
        $(this).removeClass(LEVEL_PREFIX + oldLevel);
        $(this).addClass(LEVEL_PREFIX + newLevel);
        $("th", this).removeClass("number" + oldLevel);
        $("th", this).addClass("number" + newLevel);
        $("th", this).attr("width", (3 + (newLevel - 1) * 4) + "%");
        $("td.row1", this).attr("width", (27 - (newLevel - 1) * 4) + "%");

        // Replace the sequence attribute for each of the node need to be moved up.
        $(this).attr(SEQUENCE_ATTR, newSubSeq);
        if (newSubSeq.substring(0, newSubSeq.lastIndexOf(".")) != "") {
            $(this).attr(PARENT_ATTR, newSubSeq.substring(0, newSubSeq.lastIndexOf(".")));
        } else {
            $(this).attr(PARENT_ATTR, ".");
        }
    });
}

function calcLevel(sequence) {
    return sequence.replace(/[^\.]/g, "").length + 1;
}

function showInclude() {
    $("#include").load("include.html");
}

function moveInAvailable(sequence) {
    var flag = true;

    if (calcLevel(sequence) > 4)
        return false;
    else {
        getChildNodesBySequence(sequence).each(function () {
            if (calcLevel($(this).attr(SEQUENCE_ATTR)) > 4) {
                flag = false;
            }
        });

        return flag;
    }

    return true;
}